package com.luocc.tim.recycler;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Scroller;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import com.luocc.tim.R;
import com.luocc.tim.utils.LogUtils;
import com.luocc.tim.utils.TimConstants;
import com.luocc.tim.utils.TimUtils;

public class ChatListRecycler extends RecyclerView {

    public static final String TAG = "ChatListRecycler";
    private static final int INVALID_CHILD_WIDTH = -1;  // 子ItemView不含两个子View
    private static final int VELOCITY_MIN = 300;   // 最小滑动速度

    private VelocityTracker tracker;   // 速度追踪器
    private final int mTouchSlop; // 认为是滑动的最小距离（一般由系统提供）
    private final Scroller mScroller;
    private float mStartX;
    private float mStartY;
    private ViewGroup mCurrentView;
    private int mMenuWidth = INVALID_CHILD_WIDTH;
    public static int mRefreshHeight;
    private final Handler mHandler;
    private TextView refresh;
    public static final int MSG_REFRESH_RUNNING = 1;
    public static final int MSG_REFRESH_FINISHED = 2;

    private SwipeState mState = SwipeState.IDLE;

    private enum SwipeState {
        IDLE, // 初始状态

        DRAG_VERTICAL, // 竖直方向拖拽
        RELEASE_TO_REFRESH, // 拖拽后松手刷新
        REFRESHING, // 刷新中
        CLOSING_VERTICAL, // 竖直方向关闭
        CLICK_WHILE_SLIDING_VERTICAL, //垂直滑动时点击

        DRAG_HORIZONTAL, // 水平方向拖拽
        RELEASE_TO_OPEN, // 松手后打开菜单
        CLOSING_HORIZONTAL, // 关闭菜单
        OPEN_HORIZONTAL, // 菜单已开启
        CLICK_WHILE_SLIDING_HORIZONTAL, // 水平方向滑动时点击

        CLICK, // 点击事件
    }

    private FrameLayout mParent;

    public static final String[] REFRESH_TEXT = {"刷新中.", "刷新中..", "刷新中...", "刷新中....", "刷新成功"};

    public ChatListRecycler(Context context) {
        this(context, null);
    }

    public ChatListRecycler(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ChatListRecycler(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mScroller = new Scroller(context);
        mRefreshHeight = getResources().getDimensionPixelSize(R.dimen.refresh_view_height);
        mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                if (refresh == null) {
                    refresh = findViewById(R.id.refresh);
                }
                switch (msg.what) {
                    case MSG_REFRESH_RUNNING:
                        refresh.setText((CharSequence) msg.obj);
                        if (msg.obj == REFRESH_TEXT[4]) {
                            mHandler.postDelayed(() -> refresh.setText(""), 500);
                        }
                        break;
                    case MSG_REFRESH_FINISHED:
                        mState = SwipeState.CLOSING_VERTICAL;
                        mScroller.startScroll(0, mParent.getScrollY(), 0, -mParent.getScrollY());
                        invalidate();
                        break;
                }
            }
        };
    }

    private void setCurrentView(ViewGroup viewGroup) {
        mCurrentView = viewGroup;
        int childCount = mCurrentView.getChildCount();
        if (childCount == 2) {
            mMenuWidth = mCurrentView.getChildAt(1).getWidth();
        }
    }

    /**
     * return super.onInterceptTouchEvent(e) 不会进入{@link ChatListRecycler#onTouchEvent}
     * 只要拦截了，后续的事件都不会进入本方法，而是直接进入onTouchEvent
     * return false 事件走不到onTouchEvent，继续走本方法的MOVE、UP
     * <p>
     * 思考：super和false好像一样?暂时还分不清他俩区别
     * return super的时候，事件会继续往父类上走，所以return false的时候，上下滑动失效了
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        if (mParent == null) {
            mParent = (FrameLayout) getParent();
        }
        int action = e.getAction();
        obtainVelocity(e);
        LogUtils.d(TAG, "onInterceptTouchEvent: action = " + action + ", mState = " + mState);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mStartX = e.getX();
                mStartY = e.getY();
                switch (mState) {
                    case CLICK_WHILE_SLIDING_VERTICAL:
                    case RELEASE_TO_REFRESH:
                    case REFRESHING:
                    case CLOSING_VERTICAL:
                        mState = SwipeState.CLICK_WHILE_SLIDING_VERTICAL;
                        LogUtils.d(TAG, "CLICK_ON_VERTICAL");
                        return true;
                    case CLICK_WHILE_SLIDING_HORIZONTAL:
                    case RELEASE_TO_OPEN:
                    case CLOSING_HORIZONTAL:
                        mState = SwipeState.CLICK_WHILE_SLIDING_HORIZONTAL;
                        LogUtils.d(TAG, "CLICK_ON_HORIZONTAL");
                        return true;
                }
                View childViewUnder = findChildViewUnder(e.getX(), e.getY());
                LogUtils.d(TAG, "touch on " + childViewUnder);
                if (childViewUnder instanceof ViewGroup) {
                    setCurrentView((ViewGroup) childViewUnder);
                } else {
                    mMenuWidth = INVALID_CHILD_WIDTH;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                tracker.computeCurrentVelocity(1000);
                if (isDragHorizontal(e)) {
                    LogUtils.d(TAG, "onInterceptTouchEvent: DRAG_HORIZONTAL");
                    mState = SwipeState.DRAG_HORIZONTAL;
                    return true;
                } else if (canShowRefreshArea()) {
                    LogUtils.d(TAG, "onInterceptTouchEvent: DRAG_VERTICAL");
                    mState = SwipeState.DRAG_VERTICAL;
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                releaseVelocity();
                break;
        }
        return super.onInterceptTouchEvent(e);
    }

    private boolean canShowRefreshArea() {
        return !canScrollVertically(-1) && tracker.getYVelocity() > VELOCITY_MIN;
    }

    /**
     * 1.横向速度大于阈值且大于竖向速度
     * 2.横向位移大于阈值且大于竖向位移
     * 3.满足1或2则认为是在侧滑
     * 4.速度向左为负
     *
     * @param e event
     * @return true if DRAG_HORIZONTAL
     */
    private boolean isDragHorizontal(MotionEvent e) {
        // 计算当前速度
        float xVelocity = Math.abs(tracker.getXVelocity());
        float yVelocity = Math.abs(tracker.getYVelocity());
        // 计算当前位移
        float xMove = Math.abs(e.getX() - mStartX);
        float yMove = Math.abs(e.getY() - mStartY);
        return (xVelocity > VELOCITY_MIN && xVelocity > yVelocity) || (xMove > mTouchSlop && xMove > yMove);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        int action = e.getAction();
        obtainVelocity(e);
        LogUtils.d(TAG, "onTouchEvent: action = " + action + ", mState = " + mState);
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                switch (mState) {
                    case CLICK_WHILE_SLIDING_HORIZONTAL:
                        if (!TimUtils.eventInView(mCurrentView, e)) {
                            mState = SwipeState.CLOSING_HORIZONTAL;
                            mScroller.startScroll(mCurrentView.getScrollX(), 0, -mCurrentView.getScrollX(), 0);
                            invalidate();
                        }
                        LogUtils.d(TAG, "CLOSE_HORIZONTAL");
                        return true;
                    case IDLE:
                        // FIXME: 2022/3/18 如何处理点击事件，根据上个bug
                        mState = SwipeState.CLICK;
                        return true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                tracker.computeCurrentVelocity(1000);
                float x = e.getX(), y = e.getY();
                float xMove = mStartX - x, yMove = mStartY - y;
                mStartY = y;
                mStartX = x;
                if (mState == SwipeState.DRAG_VERTICAL) {
                    LogUtils.d(TAG, "onTouchEvent: canShowRefreshArea ? " + canShowRefreshArea());
                    if (yMove < 0 && canShowRefreshArea()) {
                        mParent.scrollBy(0, (int) (yMove * TimConstants.SLIDING_COEFFICIENT));
                        return true;
                    } else if (mParent.getScrollY() < 0) {
                        if (yMove > -mParent.getScrollY()) {
                            mParent.scrollBy(0, -mParent.getScrollY());
                            break;
                        }
                        mParent.scrollBy(0, (int) yMove);
                        return true;
                    }
                    break;
                }
                if (mState == SwipeState.DRAG_HORIZONTAL) {
                    if (mCurrentView.getScrollX() + xMove >= mMenuWidth) {
                        xMove = mMenuWidth - mCurrentView.getScrollX();
                    } else if (mCurrentView.getScrollX() + xMove <= 0) {
                        xMove = -mCurrentView.getScrollX();
                    }
                    mCurrentView.scrollBy((int) xMove, 0);
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                switch (mState) {
                    case CLICK_WHILE_SLIDING_VERTICAL:
                        mState = SwipeState.CLOSING_VERTICAL;
                        mScroller.startScroll(mParent.getScrollX(), 0, -mParent.getScrollX(), 0);
                        invalidate();
                        return true;
                    case DRAG_VERTICAL:
                        if (mParent.getScrollY() > -mRefreshHeight) {
                            Log.d(TAG, "onTouchEvent: 距离不够");
                            mState = SwipeState.CLOSING_VERTICAL;
                            mScroller.startScroll(0, mParent.getScrollY(), 0, -mParent.getScrollY());
                            invalidate();
                        } else {
                            mState = SwipeState.RELEASE_TO_REFRESH;
                            mScroller.startScroll(0, mParent.getScrollY(), 0, -(mParent.getScrollY() + mRefreshHeight));
                            invalidate();
                            refresh();
                        }
                        return true;
                    case DRAG_HORIZONTAL: {
                        if (mCurrentView.getScrollX() == 0) {
                            mState = SwipeState.IDLE;
                        } else if (mCurrentView.getScrollX() == mMenuWidth) {
                            mState = SwipeState.OPEN_HORIZONTAL;
                        } else {
                            // 计算当前速度
                            tracker.computeCurrentVelocity(1000);
                            float xVelocity = tracker.getXVelocity();

                            /*
                                1.向左速度大于阈值，向左
                                2.向右速度大于阈值，向右
                                3.向左且位移大于1/3，向左
                                4.向右且位移大于1/3，向右
                            */
                            if ((xVelocity < -VELOCITY_MIN) || (xVelocity < 0 && mCurrentView.getScrollX() > mMenuWidth / 5)) {
                                mState = SwipeState.RELEASE_TO_OPEN;
                                mScroller.startScroll(mCurrentView.getScrollX(), 0, mMenuWidth - mCurrentView.getScrollX(), 0);
                            } else {
                                mState = SwipeState.CLOSING_HORIZONTAL;
                                mScroller.startScroll(mCurrentView.getScrollX(), 0, -mCurrentView.getScrollX(), 0);
                            }
                            invalidate();
                        }
                        return true;
                    }
                    case CLICK:
                        if (TimUtils.eventInView(mCurrentView.getChildAt(0), e)) {
                            mCurrentView.getChildAt(0).callOnClick();
                        }
                        return true;
                }
        }
        releaseVelocity();
        return super.onTouchEvent(e);
    }

    private void refresh() {
        LogUtils.d(TAG, "simulate refresh");
        new Thread(() -> {
            int time = 500;
            for (int i = 0; i < 5; i++) {
                Message message = mHandler.obtainMessage(MSG_REFRESH_RUNNING);
                message.obj = REFRESH_TEXT[i];
                if (i == 4) {
                    time = 200;
                }
                mHandler.sendMessage(message);
                try {
                    Thread.sleep(time);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
            }
            mHandler.sendEmptyMessage(MSG_REFRESH_FINISHED);
        }).start();
    }

    private void releaseVelocity() {
        if (tracker != null) {
            tracker.clear();
            tracker.recycle();
            tracker = null;
        }
    }

    private void obtainVelocity(MotionEvent event) {
        if (tracker == null) {
            tracker = VelocityTracker.obtain();
        }
        tracker.addMovement(event);
    }

    @Override
    public void computeScroll() {
        // computeScrollOffset判断滑动是否完成
        Log.d(TAG, "computeScroll: mState = " + mState);
        if (mScroller.computeScrollOffset()) {
            switch (mState) {
                case CLOSING_VERTICAL:
                case RELEASE_TO_REFRESH:
                    mParent.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                    invalidate();
                    break;
                case RELEASE_TO_OPEN:
                case CLOSING_HORIZONTAL:
                    mCurrentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
                    invalidate();
                    break;
                default:
                    if (mParent != null) {
                        mParent.scrollTo(0, 0);
                    }
                    if (mCurrentView != null) {
                        mCurrentView.scrollTo(0, 0);
                    }
                    LogUtils.e(TAG, "SwipeState error!: " + mState);
            }
        } else {
            switch (mState) {
                case CLOSING_VERTICAL:
                case CLOSING_HORIZONTAL:
                    mState = SwipeState.IDLE;
                    break;
                case RELEASE_TO_REFRESH:
                    mState = SwipeState.REFRESHING;
                    break;
                case RELEASE_TO_OPEN:
                    mState = SwipeState.OPEN_HORIZONTAL;
                    break;
            }
            LogUtils.d(TAG, "computeScroll end! mState = " + mState);
        }
    }
}
